#include "Resource.h"
#include "ResourceManager.h"
#include "ResourceBackgroundQueue.h"
#include "LogManager.h"
#include "Exception.h"

namespace PVM
{
	Resource::Resource(ResourceManager* creator, const std::string& name, ResourceHandle handle,
					   const std::string& group, bool isManual, ManualResourceLoader* loader)
	: mCreator(creator), mName(name), mGroup(group), mHandle(handle),
	mLoadingState(LOADSTATE_UNLOADED), mIsBackgroundLoaded(false),
	mSize(0), mIsManual(isManual), mLoader(loader), mStateCount(0)
	{
	}
	
	Resource::~Resource()
	{
	}
	
	void Resource::escalateLoading()
	{
		load(true);
		_fireLoadingComplete(true);
	}
	
	void Resource::prepare(bool background)
	{
		LoadingState old = mLoadingState.get();
		if (old != LOADSTATE_UNLOADED && old != LOADSTATE_PREPARING) return;
		
		if (!mLoadingState.cas(LOADSTATE_UNLOADED, LOADSTATE_PREPARING))
		{
			while (mLoadingState.get() == LOADSTATE_PREPARING)
			{
				PVM_LOCK_AUTO_MUTEX
			}
			
			LoadingState state = mLoadingState.get();
			if (state != LOADSTATE_PREPARED && state != LOADSTATE_LOADING && state != LOADSTATE_LOADED)
			{
				PVM_EXCEPT("Another thread failed in resource operation",
						   "Resource::prepare");
			}
			return;
		}
		
		try
		{
			PVM_LOCK_AUTO_MUTEX
			
			if (mIsManual)
			{
				if (mLoader)
				{
					mLoader->prepareResource(this);
				}
				else
				{
					LogManager::getSingleton().stream(LML_TRIVIAL)
					<< "WARNING: " << mCreator->getResourceType()
					<< " instance '" << mName << "' was defined as manually "
					<< "loaded, but no manual loader was provider. Thes Resource "
					<< "will be lost if it has to be reloaded.";
				}
			}
			else
			{
				if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)
				{
					changeGroupOwnership(
										 ResourceGroupManager::getSingleton()
										 .findGroupContainingResource(mName));
				}
				prepareImpl();
			}
		}
		catch (...)
		{
			mLoadingState.set(LOADSTATE_UNLOADED);
			throw;
		}
		
		mLoadingState.set(LOADSTATE_PREPARED);
		
		if (!background)
			_firePreparingComplete(false);
	}
	
	void Resource::load(bool background)
	{
		if (mIsBackgroundLoaded && !background) return;
		
		bool keepChecking = true;
		LoadingState old = LOADSTATE_UNLOADED;
		while (keepChecking)
		{
			old = mLoadingState.get();
			
			if (old == LOADSTATE_PREPARING)
			{
				while (mLoadingState.get() == LOADSTATE_PREPARING)
				{
					PVM_LOCK_AUTO_MUTEX
				}
				old = mLoadingState.get();
			}
			
			if (old != LOADSTATE_UNLOADED && old != LOADSTATE_PREPARED && old != LOADSTATE_LOADING) return;
			
			if (old == LOADSTATE_LOADING || !mLoadingState.cas(old, LOADSTATE_LOADING))
			{
				while (mLoadingState.get() == LOADSTATE_LOADING)
				{
					PVM_LOCK_AUTO_MUTEX
				}
				
				LoadingState state = mLoadingState.get();
				if (state == LOADSTATE_PREPARED || state == LOADSTATE_PREPARING)
				{
					continue;
				}
				else if (state != LOADSTATE_LOADED)
				{
					PVM_EXCEPT("Another thread failed in resource operation",
							   "Resource::load");
				}
				return;
			}
			keepChecking = false;
		}
		
		try
		{
			PVM_LOCK_AUTO_MUTEX
			
			if (mIsManual)
			{
				preLoadImpl();
				if (mLoader)
				{
					mLoader->loadResource(this);
				}
				else
				{
					LogManager::getSingleton().stream(LML_TRIVIAL)
					<< "WARNINGS: " << mCreator->getResourceType()
					<< " instance '" << mName << "' was defined as manually "
					<< "loaded, but no manual loader was provided. This Resource "
					<< "will be lost if it has to be reloaded.";
				}
				postLoadImpl();
			}
			else
			{
				if (old == LOADSTATE_UNLOADED)
					prepareImpl();
				
				preLoadImpl();
				
				old = LOADSTATE_PREPARED;
				
				if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)
				{
					changeGroupOwnership(
										 ResourceGroupManager::getSingleton()
										 .findGroupContainingResource(mName));
				}
				
				loadImpl();
				
				postLoadImpl();
			}
			
			mSize = calculateSize();
		}
		catch (...)
		{
			mLoadingState.set(LOADSTATE_UNLOADED);
			throw;
		}
		
		mLoadingState.set(LOADSTATE_LOADED);
		_dirtyState();
		
		if (mCreator)
			mCreator->_notifyResourceLoaded(this);
		
		if (!background)
			_fireLoadingComplete(false);
	}
	
	void Resource::_dirtyState()
	{
		++mStateCount;
	}
	
	void Resource::changeGroupOwnership(const std::string& newGroup)
	{
		if (mGroup != newGroup)
		{
			std::string oldGroup = mGroup;
			mGroup = newGroup;
			ResourceGroupManager::getSingleton()
			._notifyResourceGroupChanged(oldGroup, this);
		}
	}
	
	void Resource::unload(void)
	{
		LoadingState old = mLoadingState.get();
		if (old != LOADSTATE_LOADED && old != LOADSTATE_PREPARED) return;
		
		if (!mLoadingState.cas(old, LOADSTATE_UNLOADING)) return;
		
		{
			PVM_LOCK_AUTO_MUTEX
			if (old == LOADSTATE_PREPARED)
			{
				unprepareImpl();
			}
			else
			{
				preUnloadImpl();
				unloadImpl();
				postUnloadImpl();
			}
		}
		
		mLoadingState.set(LOADSTATE_UNLOADED);
		
		if (old == LOADSTATE_LOADED && mCreator)
			mCreator->_notifyResourceUnloaded(this);
		
		_fireUnloadingComplete();
	}
	
	void Resource::reload(void)
	{
		PVM_LOCK_AUTO_MUTEX
		if (mLoadingState.get() == LOADSTATE_LOADED)
		{
			unload();
			load();
		}
	}
	
	void Resource::touch()
	{
		load();
		
		if (mCreator)
			mCreator->_notifyResourceTouched(this);
	}
	
	void Resource::addListener(Resource::Listener* lis)
	{
		PVM_LOCK_MUTEX(mListenerListMutex)
		mListenerList.insert(lis);
	}
	
	void Resource::removeListener(Resource::Listener* lis)
	{
		PVM_LOCK_MUTEX(mListenerListMutex)
		mListenerList.erase(lis);
	}
	
	void Resource::_fireLoadingComplete(bool wasBackgroundLoaded)
	{
		PVM_LOCK_MUTEX(mListenerListMutex)
		for (ListenerList::iterator i = mListenerList.begin();
			 i != mListenerList.end(); ++i)
		{
			if (wasBackgroundLoaded)
				(*i)->backgroundLoadingComplete(this);
		}
		
		(*i)->loadingComplete(this);
	}
	
	void Resource::_firePreparingComplete(bool wasBackgroundLoaded)
	{
		PVM_LOCK_MUTEX(mListenerListMutex)
		for (Listener::iterator i = mListenerList.begin();
			 i != mListenerList.end(); ++i)
		{
			if (wasBackgroundLoaded)
				(*i)->backgroundPreparingComplete(this);
			(*i)->preparingComplete(this);
		}
	}
	
	void Resource::_fireUnloadingComplete(void)
	{
		PVM_LOCK_MUTEX(mListenerListMutex)
		for (ListenerList::iterator i = mListenerList.begin();
			 i != mListenerList.end(); ++i)
		{
			(*i)->unloadingComplete(this);
		}
	}
}
